package com.googlecode.perfectable.persistable.session;

import java.util.Map;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.googlecode.perfectable.persistable.storage.Materializer;
import com.googlecode.perfectable.persistable.storage.Persister;
import com.googlecode.perfectable.persistable.storage.Storage;

public class Cache implements Session {
	private final Persister persister;
	private final Materializer materializer;

	private final BiMap<Object, Identifier<?>> persisted = HashBiMap.create();

	public static Cache from(Storage storage) {
		return new Cache(storage, storage);
	}

	private Cache(Materializer materializer, Persister persister) {
		this.materializer = materializer;
		this.persister = persister;
	}

	private <T> Identifier<T> persist(T entity) {
		Identifier<T> candidate = (Identifier<T>) this.persisted.get(entity); // MARK
		if(candidate != null) {
			return candidate;
		}
		final Identifier<T> result = this.persister.persist(entity);
		manage(entity, result);
		return result;
	}

	@Override
	public <T> T get(Identifier<T> identity) throws InvalidReference {
		T candidate = (T) this.persisted.inverse().get(identity);
		if(candidate != null) {
			return candidate;
		}
		final T result = this.materializer.materialize(identity);
		manage(result, identity);
		return result;
	}

	public void clear() {
		this.persisted.clear();
	}

	public void flush() {
		for(Map.Entry<?, Identifier<?>> entry : this.persisted.entrySet()) {
			// MARK this should be actually handled with genericity
			final Object key = entry.getKey();
			final Identifier<? super Object> value = (Identifier<? super Object>) entry.getValue();
			this.persister.persist(key, value);
		}
		this.persisted.clear();
	}

	protected <T> void manage(T entity, final Identifier<T> result) {
		this.persisted.put(entity, result);
	}

}
